// Copyright (c) 2020 Cisco Systems and/or its affiliates.
// Licensed under the Apache License, Version 2.0 (the "License");
// that can be found in the LICENSE file in the root of the source
// tree.

package ipfix

import (
	"encoding/binary"
	"errors"
	"fmt"
	"math"
	"math/rand"
	"net/url"
	"strings"

	"github.com/intel-go/fastjson"
)

/* Field Engine offers the possibility to manipulate packets and parts of packets
by generating values in different forms and operations.
An engine has a small context and will generate values from that context */

// FieldEngineIF is a interface that every type of engine/generator should implement
// in order to provive common functionality to the caller. A caller doesn't care which
// type of field he is updating, but all the types must offer common functionality.
type FieldEngineIF interface {
	// Update updates the byte slice it receives with the new generated value.
	// It writes from the beginning of the slice.
	// If the length of the slice is shorter that the length of the variable we are
	// trying to write, it will return an error.
	// It is the responsibility of the caller to provide Update with a long enough
	// slice.
	Update(b []byte) error
	// GetOffset returns the offset of the packet as the interface was provided with.
	// The caller should use GetOffset to provide the interface with the correct
	// byte slice.
	GetOffset() uint16
	// GetSize() returns the size of the variable that the engine will write in
	// the slice byte the next time it will be called. In order to provide a slice
	// long enough, the caller should use GetSize.
	GetSize() uint16
}

// HistogramEntry is an interface for generic types of Histogram Engines.
// HistogramEngine is a fast non uniform pseudo random generator that can generate
// different types of elements. Each entry in this histogram must provide a probability
// for that entry to be chosen and a value for the histogram engine to output.
type HistogramEntry interface {
	// GetProb returns the probability of this entry to be chosen.
	GetProb() uint32
	// GetValue creates a byte buffer with the value that this entry outputs
	// in case it is picked. This value will be generated by the engine.
	// In case the input is incorrect and a value can't be generated, it will
	// return an error.
	GetValue(size uint16) ([]byte, error)
}

/* ------------------------------------------------------------------------------
								UIntEngine
--------------------------------------------------------------------------------*/
// UIntEngine params is a struct a parameters for the UIntEngine.
type UIntEngineParams struct {
	Size      uint16 `json:"size"`   // size of the uint variable in bytes
	Offset    uint16 `json:"offset"` // offset in which to write in the packet
	Op        string `json:"op"`     // operation which provides the generation, can be {inc, dec, rand}
	Step      uint64 `json:"step"`   // step to decrement or increment, rand will be ignored. Default=1.
	MinValue  uint64 `json:"min"`    // minimal value of the domain
	MaxValue  uint64 `json:"max"`    // maximal value of the domain
	InitValue uint64 `json:"init"`   // initial value in the generator, default = min value.
}

// UIntEngine is a field engine which is responsible to generate variables of uint types.
// These types can be of lengths (1, 2, 4, 8) bytes (uint8, uint16, uint32, uint64).
// The next variable can be generated through different operations, a increment of the current value,
// a decrement of the current value, or some random generation.
type UIntEngine struct {
	par       *UIntEngineParams   // params as provided by the caller
	currValue uint64              // current value in the generator
	domainLen uint64              // domain length
	mgr       *FieldEngineManager // field engine manager
}

// maxUInt64 calculates the max between 2 uint64.
func maxUInt64(a, b uint64) (max uint64) {
	if a >= b {
		max = a
	} else {
		max = b
	}
	return max
}

// findValue returns the index of the val item in array arr if it is found and true,
// else -1 and false.
func findValue(arr []uint16, val uint16) (int, bool) {
	for i, item := range arr {
		if item == val {
			return i, true
		}
	}
	return -1, false
}

// PutValue puts value in buffer depending on the size.
func PutValue(size uint16, value uint64, b []byte, mgr *FieldEngineManager) error {
	if len(b) < int(size) {
		mgr.counters.bufferTooShort++
		return fmt.Errorf("Provided slice is shorter that the size of the variable to write, want at least %v, have %v.\n", size, len(b))
	}
	switch size {
	case 1:
		b[0] = uint8(value)
	case 2:
		binary.BigEndian.PutUint16(b, uint16(value))
	case 4:
		binary.BigEndian.PutUint32(b, uint32(value))
	case 8:
		binary.BigEndian.PutUint64(b, value)
	default:
		mgr.counters.invalidSize++
		return errors.New("Size should be 1, 2, 4 or 8.")
	}
	return nil
}

func CreateUIntEngine(params *fastjson.RawMessage, mgr *FieldEngineManager) (FieldEngineIF, error) {
	// parse the json
	p := UIntEngineParams{Step: 1}
	err := mgr.tctx.UnmarshalValidate(*params, &p)
	if err != nil {
		mgr.counters.invalidJson++
		return nil, err
	}

	// create and return new engine
	return NewUIntEngine(&p, mgr)
}

// NewUintEngine creates a new uint engine
func NewUIntEngine(params *UIntEngineParams, mgr *FieldEngineManager) (*UIntEngine, error) {
	o := new(UIntEngine)
	o.mgr = mgr
	err := o.validateParams(params)
	if err != nil {
		// validate has already set the errors of the manager
		return nil, err
	}
	o.par = params
	o.domainLen = (o.par.MaxValue - o.par.MinValue + 1)
	if o.domainLen == 0 {
		// when min = 0 and max = MaxUint64 domainLen can't be represented on a uint64.
		// So we try to get as close as we can.
		o.domainLen = math.MaxUint64
	}
	if o.par.Step > o.domainLen {
		o.par.Step = o.par.Step % o.domainLen
	}
	o.currValue = maxUInt64(o.par.MinValue, o.par.InitValue)
	return o, nil
}

// ValidateParams validates the parameters of the uint engine.
func (o *UIntEngine) validateParams(params *UIntEngineParams) (err error) {
	err = nil
	if params.MinValue > params.MaxValue {
		err = fmt.Errorf("Min value %v is bigger than max value %v.\n", params.MinValue, params.MaxValue)
		o.mgr.counters.maxSmallerThanMin++
	}
	if params.InitValue != 0 && (params.InitValue < params.MinValue || params.InitValue > params.MaxValue) {
		err = fmt.Errorf("Init value %v must be between [%v - %v].\n", params.InitValue, params.MinValue, params.MaxValue)
		o.mgr.counters.badInitValue++
	}
	sizes := []uint16{1, 2, 4, 8}
	maxPossible := []uint64{math.MaxUint8, math.MaxUint16, math.MaxUint32, math.MaxUint64}
	i, ok := findValue(sizes, params.Size)
	if !ok {
		err = fmt.Errorf("Invalid size %v. Size should be {1, 2, 4, 8}.\n", params.Size)
		o.mgr.counters.invalidSize++
	} else {
		if params.MaxValue > maxPossible[i] {
			err = fmt.Errorf("Max value %v cannot be represented with size %v.\n", params.MaxValue, params.Size)
			o.mgr.counters.sizeTooSmall++
		}
	}
	if params.Op != "inc" && params.Op != "dec" && params.Op != "rand" {
		err = fmt.Errorf("Unsupported operation %v.\n", params.Op)
		o.mgr.counters.badOperation++
	}
	return err
}

// IncValue increments the value according to step
func (o *UIntEngine) IncValue() {
	// Need to be very careful here with overflows.
	left := o.par.MaxValue - o.currValue // this will never overflow as currValue < maxValue
	if o.par.Step <= left {
		// simple increment by step, not overflow of domain
		// step is fixed module size of domain
		o.currValue += o.par.Step
	} else {
		// overflow of domain
		// if here then (step > left) therefore step - left - 1 will not overflow
		o.currValue = o.par.MinValue + (o.par.Step - left - 1) // restart also consumes 1
	}
}

// DecValue decrements the value according to step
func (o *UIntEngine) DecValue() {
	left := o.currValue - o.par.MinValue // this will never overflow as currValue > minValue
	if o.par.Step <= left {
		// no overflow of domain
		// step is fixed module size of domain
		o.currValue -= o.par.Step
	} else {
		// overflow of domain
		// if here then (step > left) therefore step - left - 1 will not overflow
		o.currValue = o.par.MaxValue - (o.par.Step - left - 1) // restart also consumes 1
	}
}

// RandValue generates a random value in the domain [min, max]
func (o *UIntEngine) RandValue() {
	// Generates a uint64 with uniform distribution.
	// Converts the generated value to a value in the domain by adding the modulus of domainLength
	// to the minimal value.
	genValue := rand.Uint64()
	o.currValue = o.par.MinValue + (genValue % o.domainLen)
}

// PerformOp performs the operation, either it is rand, inc or dec.
func (o *UIntEngine) PerformOp() (err error) {
	err = nil
	switch o.par.Op {
	case "inc":
		o.IncValue()
	case "dec":
		o.DecValue()
	case "rand":
		o.RandValue()
	default:
		o.mgr.counters.badOperation++
		err = errors.New("Unrecognized operation")
	}
	return err
}

// Update implements the Update function of FieldEngineIF.
func (o *UIntEngine) Update(b []byte) error {

	err := PutValue(o.par.Size, o.currValue, b, o.mgr)
	if err != nil {
		// errors already set
		return err
	}
	err = o.PerformOp()
	if err != nil {
		// errors already set
		return err
	}
	return nil
}

// GetOffset implements the GetOffset function of FieldEngineIF.
func (o *UIntEngine) GetOffset() uint16 {
	return o.par.Offset
}

// GetSize implements the GetSize function of FieldEngineIF.
func (o *UIntEngine) GetSize() uint16 {
	return o.par.Size
}

/* ------------------------------------------------------------------------------
								UInt List Engine
--------------------------------------------------------------------------------*/
// UIntListEngine params is a struct a parameters for the UIntListEngine.
type UIntListEngineParams struct {
	Size      uint16   `json:"size"`       // size of the uint variable in bytes
	Offset    uint16   `json:"offset"`     // offset in which to write in the packet
	Op        string   `json:"op"`         // operation which provides the generation, can be {inc, dec, rand}
	Step      uint64   `json:"step"`       // step to decrement or increment, rand will be ignored. Default=1.
	InitIndex uint64   `json:"init_index"` // initial index in the list. Default=0.
	List      []uint64 `json:"list"`       // list of choices from which we pick a value and generate
}

// UIntListEngine is a field engine which is responsible to generate variables of uint types.
// These types can be of lengths (1, 2, 4, 8) bytes (uint8, uint16, uint32, uint64).
// The next variable will be picked from the list and can be picked through different operations, a increment of the current index,
// a decrement of the current index, or some random index
type UIntListEngine struct {
	par       *UIntListEngineParams // params as provided by the caller
	currIndex int                   // current index in the generator
	mgr       *FieldEngineManager   // field engine manager
}

// Create an UIntListEngine
func CreateUIntListEngine(params *fastjson.RawMessage, mgr *FieldEngineManager) (FieldEngineIF, error) {
	// parse the json
	p := UIntListEngineParams{Step: 1, InitIndex: 0}
	err := mgr.tctx.UnmarshalValidate(*params, &p)
	if err != nil {
		mgr.counters.invalidJson++
		return nil, err
	}
	// create and return new engine
	return NewUIntListEngine(&p, mgr)
}

// NewUIntListEngine creates a new uint list engine.
func NewUIntListEngine(params *UIntListEngineParams, mgr *FieldEngineManager) (*UIntListEngine, error) {
	o := new(UIntListEngine)
	o.mgr = mgr
	err := o.validateParams(params)
	if err != nil {
		// validate has already set the errors of the manager
		return nil, err
	}
	o.par = params
	if int(o.par.Step) > len(o.par.List) {
		o.par.Step = o.par.Step % uint64(len(o.par.List))
	}
	o.currIndex = int(o.par.InitIndex)
	return o, nil
}

// ValidateParams validates the parameters of the uint engine.
func (o *UIntListEngine) validateParams(params *UIntListEngineParams) (err error) {
	if params.Op != "inc" && params.Op != "dec" && params.Op != "rand" {
		err = fmt.Errorf("Unsupported operation %v.\n", params.Op)
		o.mgr.counters.badOperation++
	}
	if len(params.List) == 0 {
		err = fmt.Errorf("Engine list can't be empty.\n")
		o.mgr.counters.emptyList++
	}
	if len(params.List) > 0 && int(params.InitIndex) >= len(params.List) {
		err = fmt.Errorf("Init index %v must be between [%v - %v].\n", params.InitIndex, 0, len(params.List))
		o.mgr.counters.badInitValue++
	}
	sizes := []uint16{1, 2, 4, 8}
	maxPossible := []uint64{math.MaxUint8, math.MaxUint16, math.MaxUint32, math.MaxUint64}
	i, ok := findValue(sizes, params.Size)
	if !ok {
		err = fmt.Errorf("Invalid size %v. Size should be {1, 2, 4, 8}.\n", params.Size)
		o.mgr.counters.invalidSize++
	} else {
		for j := range params.List {
			if params.List[j] > maxPossible[i] {
				err = fmt.Errorf("List value %v cannot be represented with size %v.\n", params.List[j], params.Size)
				o.mgr.counters.sizeTooSmall++
				break
			}
		}
	}
	return err
}

// IncValue increments the value according to step
func (o *UIntListEngine) IncValue() {
	o.currIndex = (o.currIndex + int(o.par.Step)) % len(o.par.List)
}

// DecValue decrements the value according to step
func (o *UIntListEngine) DecValue() {
	// Careful with overflows
	if o.currIndex < int(o.par.Step) {
		// Overflows
		o.currIndex = len(o.par.List) - (int(o.par.Step) - o.currIndex)
	} else {
		// No overflow
		o.currIndex = o.currIndex - int(o.par.Step)
	}

}

// RandValue generates a new index in the list.
func (o *UIntListEngine) RandValue() {
	o.currIndex = rand.Intn(len(o.par.List))
}

// PerformOp performs the operation, either it is rand, inc or dec.
func (o *UIntListEngine) PerformOp() (err error) {
	err = nil
	switch o.par.Op {
	case "inc":
		o.IncValue()
	case "dec":
		o.DecValue()
	case "rand":
		o.RandValue()
	default:
		o.mgr.counters.badOperation++
		err = errors.New("Unrecognized operation")
	}
	return err
}

// Update implements the Update function of FieldEngineIF.
func (o *UIntListEngine) Update(b []byte) error {

	err := PutValue(o.par.Size, o.par.List[o.currIndex], b, o.mgr)
	if err != nil {
		// errors already set
		return err
	}

	err = o.PerformOp()
	if err != nil {
		// errors already set
		return err
	}
	return nil
}

// GetOffset implements the GetOffset function of FieldEngineIF.
func (o *UIntListEngine) GetOffset() uint16 {
	return o.par.Offset
}

// GetSize implements the GetSize function of FieldEngineIF.
func (o *UIntListEngine) GetSize() uint16 {
	return o.par.Size
}

/* ------------------------------------------------------------------------------
								String List Engine
--------------------------------------------------------------------------------*/
// StringListEngineParams is a struct a parameters for the StringListEngine.
type StringListEngineParams struct {
	Size         uint16   `json:"size"`          // size of maximal string we are going to write
	Offset       uint16   `json:"offset"`        // offset in which to write in the packet
	Op           string   `json:"op"`            // operation which provides the generation in the list, can be {inc, dec, rand}
	Step         uint64   `json:"step"`          // step to decrement or increment, rand will be ignored. Default=1.
	InitIndex    uint64   `json:"init_index"`    // initial index in the list. Default=0.
	List         []string `json:"list"`          // list of choices from which we pick a value and generate
	PaddingValue uint8    `json:"padding_value"` // value which will be used to pad the string to maximal size
}

// StringListEngine is a field engine which is responsible to generate variables of string types.
// The strings can be any length as long as they are shorter than size. They will be padded with the padding_value to size length.
// The next variable will be picked from the list and can be picked through different operations, a increment of the current index,
// a decrement of the current index, or some random index
type StringListEngine struct {
	par           *StringListEngineParams // params as provided by the caller
	currIndex     int                     // current index in the generator
	processedList [][]byte                // list of bytes manipulated and padded ahead of time for fast update
	mgr           *FieldEngineManager     // field engine manager
}

// Create a StingListEngine
func CreateStringListEngine(params *fastjson.RawMessage, mgr *FieldEngineManager) (FieldEngineIF, error) {
	// parse the json
	p := StringListEngineParams{Step: 1, InitIndex: 0, PaddingValue: 0}
	err := mgr.tctx.UnmarshalValidate(*params, &p)
	if err != nil {
		mgr.counters.invalidJson++
		return nil, err
	}
	// create and return new engine
	return NewStringListEngine(&p, mgr)
}

// NewStringListEngine creates a new string list engine.
func NewStringListEngine(params *StringListEngineParams, mgr *FieldEngineManager) (*StringListEngine, error) {
	o := new(StringListEngine)
	o.mgr = mgr
	err := o.validateParams(params)
	if err != nil {
		// validate has already set the errors of the manager
		return nil, err
	}
	o.par = params
	if int(o.par.Step) > len(o.par.List) {
		o.par.Step = o.par.Step % uint64(len(o.par.List))
	}
	o.processStrings()
	o.currIndex = int(o.par.InitIndex)
	return o, nil
}

// ValidateParams validates the parameters of the string list engine.
func (o *StringListEngine) validateParams(params *StringListEngineParams) (err error) {
	if params.Op != "inc" && params.Op != "dec" && params.Op != "rand" {
		err = fmt.Errorf("Unsupported operation %v.\n", params.Op)
		o.mgr.counters.badOperation++
	}
	if len(params.List) == 0 {
		err = fmt.Errorf("Engine list can't be empty.\n")
		o.mgr.counters.emptyList++
	}
	if len(params.List) > 0 && int(params.InitIndex) >= len(params.List) {
		err = fmt.Errorf("Init index %v must be between [%v - %v].\n", params.InitIndex, 0, len(params.List))
		o.mgr.counters.badInitValue++
	}
	for _, str := range params.List {
		if len([]byte(str)) > int(params.Size) {
			err = fmt.Errorf("String %v cannot be represented with size %v.\n", str, params.Size)
			o.mgr.counters.sizeTooSmall++
			break
		}
	}
	return err
}

// processStrings pre processes the strings and paddes them ahead of time in order to perform fast update
// and no need to pad every time.
func (o *StringListEngine) processStrings() {
	for _, str := range o.par.List {
		bytesStr := []byte(str)
		appendSize := int(o.par.Size) - len(bytesStr)
		for j := 0; j < appendSize; j++ {
			bytesStr = append(bytesStr, byte(o.par.PaddingValue))
		}
		o.processedList = append(o.processedList, bytesStr)
	}
}

// IncValue increments the value according to step
func (o *StringListEngine) IncValue() {
	o.currIndex = (o.currIndex + int(o.par.Step)) % len(o.par.List)
}

// DecValue decrements the value according to step
func (o *StringListEngine) DecValue() {
	// Careful with overflows
	if o.currIndex < int(o.par.Step) {
		// Overflows
		o.currIndex = len(o.par.List) - (int(o.par.Step) - o.currIndex)
	} else {
		// No overflow
		o.currIndex = o.currIndex - int(o.par.Step)
	}

}

// RandValue generates a new index in the list.
func (o *StringListEngine) RandValue() {
	o.currIndex = rand.Intn(len(o.par.List))
}

// PerformOp performs the operation, either it is rand, inc or dec.
func (o *StringListEngine) PerformOp() (err error) {
	err = nil
	switch o.par.Op {
	case "inc":
		o.IncValue()
	case "dec":
		o.DecValue()
	case "rand":
		o.RandValue()
	default:
		o.mgr.counters.badOperation++
		err = errors.New("Unrecognized operation")
	}
	return err
}

// Update implements the Update function of FieldEngineIF.
func (o *StringListEngine) Update(b []byte) error {
	if len(b) < int(o.par.Size) {
		o.mgr.counters.bufferTooShort++
		return fmt.Errorf("Provided slice is shorter that the size of the variable to write, want at least %v, have %v.\n", o.par.Size, len(b))
	}

	copied := copy(b, o.processedList[o.currIndex])
	if copied != int(o.par.Size) {
		o.mgr.counters.badCopyToBuffer++
		return fmt.Errorf("Error happened copying the string into the buffer. Copied bytes: want %v and have %v.\n", o.par.Size, copied)
	}

	err := o.PerformOp()
	if err != nil {
		// errors already set
		return err
	}
	return nil
}

// GetOffset implements the GetOffset function of FieldEngineIF.
func (o *StringListEngine) GetOffset() uint16 {
	return o.par.Offset
}

// GetSize implements the GetSize function of FieldEngineIF.
func (o *StringListEngine) GetSize() uint16 {
	return o.par.Size
}

/* ------------------------------------------------------------------------------
							HistogramEngine
--------------------------------------------------------------------------------*/
// HistogramEngineCommonParams contains the common params for all the types of
// histogram engines.
type HistogramEngineCommonParams struct {
	Size   uint16 `json:"size"`   // size of the variable in bytes
	Offset uint16 `json:"offset"` // offset in which to write in the packet
}

// HistogramEngineParams is a struct that must be provided to the HistogramEngine
// when creating it.
type HistogramEngineParams struct {
	HistogramEngineCommonParams                  // common params for all the engines
	Entries                     []HistogramEntry // the entries of the histogram
}

// HistogramEngine is a FieldEngine, which contains a non uniform pseudo random
// generator and can generate it's entries as per probability of each entry.
type HistogramEngine struct {
	par           *HistogramEngineParams // params as provided by the caller
	distributions []uint32               // distribution slice
	generator     *NonUniformRandGen     // non uniform random generator of the distribution
	mgr           *FieldEngineManager    // field engine manager

}

// NewHistogramEngine creates a new HistogramEngine from the HistogramEngineParams provided.
func NewHistogramEngine(params *HistogramEngineParams, mgr *FieldEngineManager) (o *HistogramEngine, err error) {
	o = new(HistogramEngine)
	o.buildDistributionSlice(params.Entries)
	o.par = params
	o.mgr = mgr
	o.generator, err = NewNonUniformRandGen(o.distributions)
	if err != nil {
		o.mgr.counters.generatorCreationError++
		return nil, err
	}
	return o, nil
}

func (o *HistogramEngine) buildDistributionSlice(entries []HistogramEntry) {
	for _, entry := range entries {
		o.distributions = append(o.distributions, entry.GetProb())
	}
}

// Update implements the Update function of FieldEngineIF.
func (o *HistogramEngine) Update(b []byte) error {
	if len(b) < int(o.par.Size) {
		o.mgr.counters.bufferTooShort++
		return fmt.Errorf("Provided slice is shorter that the size of the variable to write, want at least %v, have %v.\n", o.par.Size, len(b))
	}
	// clean the provided buffer
	copiedSize := copy(b[:o.par.Size], make([]byte, o.par.Size))
	if copiedSize != int(o.par.Size) {
		o.mgr.counters.badCopyToBuffer++
		return fmt.Errorf("Didn't copy the right amount to the buffer, want %v have %v.\n", o.par.Size, copiedSize)
	}
	entryIndex := o.generator.Generate()
	entry := o.par.Entries[entryIndex]
	newValueBytes, err := entry.GetValue(o.par.Size)
	if err != nil {
		o.mgr.counters.invalidHistogramEntry++
		return err
	}
	if len(newValueBytes) > int(o.par.Size) {
		o.mgr.counters.sizeTooSmall++
		return fmt.Errorf("Size %v is too small for generated value with length %v.\n", o.par.Size, len(newValueBytes))
	}
	copiedSize = copy(b[:o.par.Size], newValueBytes)
	if copiedSize != len(newValueBytes) {
		o.mgr.counters.badCopyToBuffer++
		return fmt.Errorf("Didn't copy the right amount to the buffer, want %v have %v.\n", len(newValueBytes), copiedSize)
	}
	return nil
}

// GetOffset implements the GetOffset function of FieldEngineIF.
func (o *HistogramEngine) GetOffset() uint16 {
	return o.par.Offset
}

// GetSize implements the GetSize function of FieldEngineIF.
func (o *HistogramEngine) GetSize() uint16 {
	return o.par.Size
}

/* ------------------------------------------------------------------------------
						Helping Utils for Uint32
--------------------------------------------------------------------------------*/

func putValueInBuffer(size uint16, value uint32) (b []byte, err error) {
	b = make([]byte, size)
	switch size {
	case 1:
		b[0] = uint8(value)
	case 2:
		binary.BigEndian.PutUint16(b, uint16(value))
	case 4:
		binary.BigEndian.PutUint32(b, value)
	default:
		return nil, fmt.Errorf("Invalid size %v in GetValue!\n", size)
	}
	return b, nil
}

/* ------------------------------------------------------------------------------
						HistogramUInt32Entry
--------------------------------------------------------------------------------*/
// HistogramUInt32Entry represents a uint32 which can be used as an entry for the
// HistogramEngine. This entry can be picked with probability prob.
type HistogramUInt32Entry struct {
	V    uint32 `json:"v"`    // a value v of 32 bits
	Prob uint32 `json:"prob"` // probability of this entry
}

// HistogramUInt32Params is used for parsing the input json.
type HistogramUInt32Params struct {
	HistogramEngineCommonParams                        // common params
	Entries                     []HistogramUInt32Entry `json:"entries"` // slice of entries
}

// GetValue puts the value in the byte buffer.
func (o *HistogramUInt32Entry) GetValue(size uint16) (b []byte, err error) {
	return putValueInBuffer(size, o.V)
}

// GetProb returns the probability for this entry to be picked in the histogram engine.
func (o *HistogramUInt32Entry) GetProb() uint32 {
	return o.Prob
}

// CreateHistogramUInt32ListEngine creates an histogram engine of uint 32 from the input json.
func CreateHistogramUInt32Engine(params *fastjson.RawMessage, mgr *FieldEngineManager) (eng FieldEngineIF, err error) {
	// unmarshal the data
	p := HistogramUInt32Params{}
	err = fastjson.Unmarshal(*params, &p)
	if err != nil {
		mgr.counters.invalidJson++
		return nil, err
	}

	// create general params
	var histParams HistogramEngineParams
	histParams.Size = p.Size
	histParams.Offset = p.Offset
	for i := range p.Entries {
		switch p.Size {
		case 1:
			if p.Entries[i].V > 0xFF {
				mgr.counters.invalidSize++
				return nil, fmt.Errorf("Value %v can't be represented with %v bytes\n.", p.Entries[i].V, p.Size)
			}
		case 2:
			if p.Entries[i].V > 0xFFFF {
				mgr.counters.invalidSize++
				return nil, fmt.Errorf("Value %v can't be represented with %v bytes\n.", p.Entries[i].V, p.Size)
			}
		case 8:
			mgr.counters.invalidSize++
			return nil, fmt.Errorf("Invalid size %v with type of engine.\n", p.Size)
		}
		histParams.Entries = append(histParams.Entries, &p.Entries[i])
	}

	// create and return new engine
	return NewHistogramEngine(&histParams, mgr)
}

/* ------------------------------------------------------------------------------
						HistogramUInt32RangeEntry
--------------------------------------------------------------------------------*/
// HistogramUInt32RangeEntry represents a range of uint32 which can be used as an
// entry for the HistogramEngine. This entry can be picked with probability prob.
// If the entry is picked, a value in the range will be generated uniformly.
type HistogramUInt32RangeEntry struct {
	Min  uint32 `json:"min"`  // lower bound of the range
	Max  uint32 `json:"max"`  // higher bound of the range
	Prob uint32 `json:"prob"` // probability of this entry
}

// HistogramUInt32RangeParams is used for parsing the input json.
type HistogramUInt32RangeParams struct {
	HistogramEngineCommonParams                             // common params
	Entries                     []HistogramUInt32RangeEntry `json:"entries"` // slice of entries
}

// GetValue generates uniformly a value in the range and puts it in the byte buffer.
func (o *HistogramUInt32RangeEntry) GetValue(size uint16) (b []byte, err error) {
	if o.Max < o.Min {
		return nil, fmt.Errorf("Max %v is smaller than min %v in HistogramRuneRangeEntry.\n", o.Max, o.Min)
	}
	v := rand.Uint32()                    // generate random 32 bytes
	v = o.Min + (v % (o.Max - o.Min + 1)) // scale it to the domain
	return putValueInBuffer(size, v)
}

// GetProb returns the probability for this entry to be picked in the histogram engine.
func (o *HistogramUInt32RangeEntry) GetProb() uint32 {
	return o.Prob
}

// CreateHistogramUInt32RangeEngine creates an histogram engine of uint32 range from the input json.
func CreateHistogramUInt32RangeEngine(params *fastjson.RawMessage, mgr *FieldEngineManager) (eng FieldEngineIF, err error) {
	// unmarshal the data
	p := HistogramUInt32RangeParams{}
	err = fastjson.Unmarshal(*params, &p)
	if err != nil {
		mgr.counters.invalidJson++
		return nil, err
	}

	// create general params
	var histParams HistogramEngineParams
	histParams.Size = p.Size
	histParams.Offset = p.Offset
	for i := range p.Entries {
		if p.Entries[i].Min > p.Entries[i].Max {
			mgr.counters.maxSmallerThanMin++
			return nil, fmt.Errorf("Min %v bigger than max %v in entry #%v.\n", p.Entries[i].Min, p.Entries[i].Max, i)
		}
		switch p.Size {
		case 1:
			if p.Entries[i].Max > 0xFF {
				mgr.counters.invalidSize++
				return nil, fmt.Errorf("Max %v can't be represented with %v bytes\n.", p.Entries[i].Max, p.Size)
			}
		case 2:
			if p.Entries[i].Max > 0xFFFF {
				mgr.counters.invalidSize++
				return nil, fmt.Errorf("Max %v can't be represented with %v bytes\n.", p.Entries[i].Max, p.Size)
			}
		case 8:
			mgr.counters.invalidSize++
			return nil, fmt.Errorf("Invalid size %v with type of engine.\n", p.Size)
		}

		histParams.Entries = append(histParams.Entries, &p.Entries[i])
	}

	// create and return new engine
	return NewHistogramEngine(&histParams, mgr)
}

/* ------------------------------------------------------------------------------
						HistogramUInt32ListEntry
--------------------------------------------------------------------------------*/
// HistogramUInt32ListEntry represents a list of uint32 which can be used as an
// entry for the HistogramEngine. This entry can be picked with probability prob.
// If the entry is picked, a value in the list will be selected uniformly.
type HistogramUInt32ListEntry struct {
	List []uint32 `json:"list"` // a list from where the element will be picked
	Prob uint32   `json:"prob"` // probability of this entry
}

// HistogramUInt32ListParams is used for parsing the input json.
type HistogramUInt32ListParams struct {
	HistogramEngineCommonParams                            // common params
	Entries                     []HistogramUInt32ListEntry `json:"entries"` // slice of entries
}

// GetValue picks a random value from the list and puts it in the byte buffer.
func (o *HistogramUInt32ListEntry) GetValue(size uint16) (b []byte, err error) {
	if o.List == nil || len(o.List) == 0 {
		return nil, fmt.Errorf("Empty list in HistogramUInt32ListEntry.\n")
	}
	index := rand.Intn(len(o.List))
	return putValueInBuffer(size, o.List[index])
}

// GetProb returns the probability for this entry to be picked in the histogram engine.
func (o *HistogramUInt32ListEntry) GetProb() uint32 {
	return o.Prob
}

// CreateHistogramUInt32ListEngine creates an histogram engine of uint32 list from the input json.
func CreateHistogramUInt32ListEngine(params *fastjson.RawMessage, mgr *FieldEngineManager) (eng FieldEngineIF, err error) {
	// unmarshal the data
	p := HistogramUInt32ListParams{}
	err = fastjson.Unmarshal(*params, &p)
	if err != nil {
		mgr.counters.invalidJson++
		return nil, err
	}

	// create general params
	var histParams HistogramEngineParams
	histParams.Size = p.Size
	histParams.Offset = p.Offset
	for i := range p.Entries {
		if len(p.Entries[i].List) == 0 {
			mgr.counters.emptyList++
			return nil, fmt.Errorf("Entry # %v contains an empty list.\n", i)
		}
		for j := range p.Entries[i].List {
			v := p.Entries[i].List[j]
			switch p.Size {
			case 1:
				if v > 0xFF {
					mgr.counters.invalidSize++
					return nil, fmt.Errorf("List value %v can't be represented with %v bytes\n.", v, p.Size)
				}
			case 2:
				if v > 0xFFFF {
					mgr.counters.invalidSize++
					return nil, fmt.Errorf("List value %v can't be represented with %v bytes\n.", v, p.Size)
				}
			case 8:
				mgr.counters.invalidSize++
				return nil, fmt.Errorf("Invalid size %v with type of engine.\n", p.Size)
			}
		}
		histParams.Entries = append(histParams.Entries, &p.Entries[i])
	}

	// create and return new engine
	return NewHistogramEngine(&histParams, mgr)
}

/* ------------------------------------------------------------------------------
						HistogramUInt64Entry
--------------------------------------------------------------------------------*/
// HistogramUInt64Entry represents a uint64 which can be used as an entry for the
// HistogramEngine. This entry can be picked with probability prob.
type HistogramUInt64Entry struct {
	V    uint64 `json:"v"`    // a value v of 64 bits
	Prob uint32 `json:"prob"` // probability of this entry
}

// HistogramUInt64Params is used for parsing the input json.
type HistogramUInt64Params struct {
	HistogramEngineCommonParams                        // common params
	Entries                     []HistogramUInt64Entry `json:"entries"` // slice of entries
}

// GetValue puts the value in the byte buffer.
func (o *HistogramUInt64Entry) GetValue(size uint16) (b []byte, err error) {
	if size != 8 {
		return nil, fmt.Errorf("Size in HistogramUInt64Entry GetValue is %v, want %v.\n", size, 8)
	}
	b = make([]byte, size)
	binary.BigEndian.PutUint64(b, o.V)
	return b, nil
}

// GetProb returns the probability for this entry to be picked in the histogram engine.
func (o *HistogramUInt64Entry) GetProb() uint32 {
	return o.Prob
}

// CreateHistogramUInt64Engine creates an histogram engine of uint64 from the input json.
func CreateHistogramUInt64Engine(params *fastjson.RawMessage, mgr *FieldEngineManager) (eng FieldEngineIF, err error) {
	// unmarshal the data
	p := HistogramUInt64Params{}
	err = fastjson.Unmarshal(*params, &p)
	if err != nil {
		mgr.counters.invalidJson++
		return nil, err
	}
	if p.Size != 8 {
		mgr.counters.invalidSize++
		return nil, fmt.Errorf("Invalid size %v with type of engine.\n", p.Size)
	}

	// create general params
	var histParams HistogramEngineParams
	histParams.Size = p.Size
	histParams.Offset = p.Offset
	for i := range p.Entries {
		histParams.Entries = append(histParams.Entries, &p.Entries[i])
	}

	// create and return new engine
	return NewHistogramEngine(&histParams, mgr)
}

/* ------------------------------------------------------------------------------
						HistogramUInt64RangeEntry
--------------------------------------------------------------------------------*/
// HistogramUInt64RangeEntry represents a range of uint64 which can be used as an
// entry for the HistogramEngine. This entry can be picked with probability prob.
// If the entry is picked, a value in the range will be generated uniformly.
type HistogramUInt64RangeEntry struct {
	Min  uint64 `json:"min"`  // lower bound of the range
	Max  uint64 `json:"max"`  // higher bound of the range
	Prob uint32 `json:"prob"` // probability of this entry
}

// HistogramUInt64RangeParams is used for parsing the input json.
type HistogramUInt64RangeParams struct {
	HistogramEngineCommonParams                             // common params
	Entries                     []HistogramUInt64RangeEntry `json:"entries"` // slice of entries
}

// GetValue generates uniformly a value in the range and puts it in the byte buffer.
func (o *HistogramUInt64RangeEntry) GetValue(size uint16) (b []byte, err error) {
	if size != 8 {
		return nil, fmt.Errorf("Size in HistogramUInt64Entry GetValue is %v, want %v.\n", size, 8)
	}
	if o.Max < o.Min {
		return nil, fmt.Errorf("Max %v is smaller than min %v in HistogramRuneRangeEntry.\n", o.Max, o.Min)
	}
	b = make([]byte, size)
	v := rand.Uint64()                    // generate random 32 bytes
	v = o.Min + (v % (o.Max - o.Min + 1)) // scale it to the domain
	binary.BigEndian.PutUint64(b, v)      // put it in the bytes buffer
	return b, nil
}

// GetProb returns the probability for this entry to be picked in the histogram engine.
func (o *HistogramUInt64RangeEntry) GetProb() uint32 {
	return o.Prob
}

// CreateHistogramUInt64RangeEngine creates an histogram engine of uint64 range from the input json.
func CreateHistogramUInt64RangeEngine(params *fastjson.RawMessage, mgr *FieldEngineManager) (eng FieldEngineIF, err error) {
	// unmarshal the data
	p := HistogramUInt64RangeParams{}
	err = fastjson.Unmarshal(*params, &p)
	if err != nil {
		mgr.counters.invalidJson++
		return nil, err
	}

	if p.Size != 8 {
		mgr.counters.invalidSize++
		return nil, fmt.Errorf("Invalid size %v with type of engine.\n", p.Size)
	}

	// create general params
	var histParams HistogramEngineParams
	histParams.Size = p.Size
	histParams.Offset = p.Offset
	for i := range p.Entries {
		if p.Entries[i].Min > p.Entries[i].Max {
			mgr.counters.maxSmallerThanMin++
			return nil, fmt.Errorf("Min %v bigger than max %v in entry #%v.\n", p.Entries[i].Min, p.Entries[i].Max, i)
		}
		histParams.Entries = append(histParams.Entries, &p.Entries[i])
	}

	// create and return new engine
	return NewHistogramEngine(&histParams, mgr)
}

/* ------------------------------------------------------------------------------
						HistogramUInt64ListEntry
--------------------------------------------------------------------------------*/
// HistogramUInt64ListEntry represents a list of uint64 which can be used as an
// entry for the HistogramEngine. This entry can be picked with probability prob.
// If the entry is picked, a value in the list will be selected uniformly.
type HistogramUInt64ListEntry struct {
	List []uint64 `json:"list"` // a list from where the element will be picked
	Prob uint32   `json:"prob"` // probability of this entry
}

// HistogramUInt64ListParams is used for parsing the input json.
type HistogramUInt64ListParams struct {
	HistogramEngineCommonParams                            // common params
	Entries                     []HistogramUInt64ListEntry `json:"entries"` // slice of entries
}

// GetValue picks a random value from the list and puts it in the byte buffer.
func (o *HistogramUInt64ListEntry) GetValue(size uint16) (b []byte, err error) {
	if size != 8 {
		return nil, fmt.Errorf("Size in HistogramUInt64Entry GetValue is %v, want %v.\n", size, 8)
	}
	if o.List == nil || len(o.List) == 0 {
		return nil, fmt.Errorf("Empty list in HistogramUInt32ListEntry.\n")
	}
	b = make([]byte, size)
	index := rand.Intn(len(o.List))
	binary.BigEndian.PutUint64(b, o.List[index])
	return b, nil
}

// GetProb returns the probability for this entry to be picked in the histogram engine.
func (o *HistogramUInt64ListEntry) GetProb() uint32 {
	return o.Prob
}

// CreateHistogramUInt32ListEngine creates an histogram engine of uint32 list from the input json.
func CreateHistogramUInt64ListEngine(params *fastjson.RawMessage, mgr *FieldEngineManager) (eng FieldEngineIF, err error) {
	// unmarshal the data
	p := HistogramUInt64ListParams{}
	err = fastjson.Unmarshal(*params, &p)
	if err != nil {
		mgr.counters.invalidJson++
		return nil, err
	}

	if p.Size != 8 {
		mgr.counters.invalidSize++
		return nil, fmt.Errorf("Invalid size %v with type of engine.\n", p.Size)
	}

	// create general params
	var histParams HistogramEngineParams
	histParams.Size = p.Size
	histParams.Offset = p.Offset
	for i := range p.Entries {
		if len(p.Entries[i].List) == 0 {
			mgr.counters.emptyList++
			return nil, fmt.Errorf("Entry # %v contains an empty list.\n", i)
		}
		histParams.Entries = append(histParams.Entries, &p.Entries[i])
	}

	// create and return new engine
	return NewHistogramEngine(&histParams, mgr)
}

/* ------------------------------------------------------------------------------
					 	Time Start Engine
--------------------------------------------------------------------------------*/
// TimeStartEngineParams represents the paramaters for a TimeStartEngine engine.
type TimeStartEngineParams struct {
	Size              uint16 `json:"size"`                                     // Size of the time field in bytes. Can be 4 or 8.
	Offset            uint16 `json:"offset"`                                   // Offset in which to write in the packet
	TimeEndEngineName string `json:"time_end_engine_name" validate:"required"` // Name of the time end engine.
	UptimeOffset      uint64 `json:"time_offset"`                              // The first packet offset from the starting point in milliseconds.
	// In case of relative time from systime provide the offset to uptime. In case of absolute of time, provide the Unix time.
	InterPacketGapMin uint64 `json:"ipg_min" validate:"required"` // Minimal Inter Packet Gap of this flow in milliseconds.
	InterPacketGapMax uint64 `json:"ipg_max" validate:"required"` // Maximal Inter Packet Gap of this flow in milliseconds.
	// The next flow will have an Inter Packet Gap between the minimal and maximal IPG from the end of the previous flow.
}

// TimeStartEngine represents an engine for time beginning fields. (relative to uptime for example, or absolute Unix time).
// This engine should be used together with a TimeEndEngine.
type TimeStartEngine struct {
	par             *TimeStartEngineParams // Parameters
	mgr             *FieldEngineManager    // Engine Manager
	newFlowStart    uint64                 // Relative time for this flow to begin.
	previousFlowEnd uint64                 // Relative time since the previous flow ended. This value is updated by the TimeEndEngine.
	firstPacket     bool                   // Flag representing if this is the first packet
	timeEndEngine   *TimeEndEngine         // TimeEndEngine
}

// CreateTimeStartEngine creates a TimeStartEngine.
func CreateTimeStartEngine(params *fastjson.RawMessage, mgr *FieldEngineManager) (FieldEngineIF, error) {
	// parse the json
	p := TimeStartEngineParams{UptimeOffset: 0}
	err := mgr.tctx.UnmarshalValidate(*params, &p)
	if err != nil {
		mgr.counters.invalidJson++
		return nil, err
	}
	// create and return new engine
	return NewTimeStartEngine(&p, mgr)
}

// NewTimeStartEngine creates a new TimeStart engine. This engine can be used for fields such as
// flowStartSysUpTime etc.
func NewTimeStartEngine(params *TimeStartEngineParams, mgr *FieldEngineManager) (*TimeStartEngine, error) {
	o := new(TimeStartEngine)
	o.mgr = mgr
	err := o.validateParams(params)
	if err != nil {
		// validate has already set the errors of the manager
		return nil, err
	}
	o.par = params
	o.newFlowStart = o.par.UptimeOffset
	o.firstPacket = true
	return o, nil
}

// validateParams validates that the parameters are correct.
func (o *TimeStartEngine) validateParams(params *TimeStartEngineParams) (err error) {
	if params.Size != 4 && params.Size != 8 {
		o.mgr.counters.invalidSize++
		err = fmt.Errorf("Size for this engine must be 4 or 8. Invalid size %v.\n", params.Size)
	}

	if params.InterPacketGapMin > params.InterPacketGapMax {
		o.mgr.counters.maxSmallerThanMin++
		err = fmt.Errorf("InterPacketGap min %v is greater than InterPacketGap max %v.\n", params.InterPacketGapMin, params.InterPacketGapMax)
	}

	if params.Size == 4 && (params.InterPacketGapMax > 0xFFFFFFFF || params.UptimeOffset > 0xFFFFFFFF) {
		o.mgr.counters.sizeTooSmall++
		err = fmt.Errorf("Size too small, can't represent max value or uptime offset with %v bytes.\n", params.Size)
	}

	return err
}

// setPreviousFlowEndTime is a setter provided to the TimeEnd engine to set the previous flow timestamp (the one that is about to finishfor End engine.)
func (o *TimeStartEngine) setPreviousFlowEndTime(previousFlowStartTime uint64) {
	o.previousFlowEnd = previousFlowStartTime
}

// getNewFlowStartValue generates a relative timestamp for the beginning of the new flow.
func (o *TimeStartEngine) getNewFlowStartValue() (value uint64) {
	if o.firstPacket {
		value = o.newFlowStart
		o.firstPacket = false
	} else {
		// Generates a uint64 with uniform distribution.
		// Converts the generated value to a value in the domain [min-max]. This way we get an ipg.
		genValue := rand.Uint64()
		ipg := o.par.InterPacketGapMin + (genValue % (o.par.InterPacketGapMax - o.par.InterPacketGapMin + 1))
		// Assumes the TimeEnd engine has updated the previous flow end.
		value = o.previousFlowEnd + ipg
	}
	return value
}

// Update implements the Update function of FieldEngineIF.
func (o *TimeStartEngine) Update(b []byte) error {

	// Get the Time End engine. This can't be done upon creation as the engine might not be created yet.
	if o.timeEndEngine == nil {
		timeEndEngine, ok := o.mgr.engines[o.par.TimeEndEngineName]
		if !ok {
			return fmt.Errorf("TimeEnd engine name %v not found in engine manager database. Must provide this engine.\n", o.par.TimeEndEngineName)
		}
		o.timeEndEngine, ok = timeEndEngine.(*TimeEndEngine)
		if !ok {
			o.mgr.counters.badEngineType++
			return fmt.Errorf("Failed converting engine %v to TimeEnd. Make sure the engine type is corrent.\n", o.par.TimeEndEngineName)
		}
	}

	// Get a value for the new flow start time.
	value := o.getNewFlowStartValue()

	// Update the buffer
	switch o.par.Size {
	case 4:
		binary.BigEndian.PutUint32(b, uint32(value))
	case 8:
		binary.BigEndian.PutUint64(b, value)
	default:
		o.mgr.counters.invalidSize++
		return errors.New("Size should be 1, 2, 4 or 8.")
	}

	// Update the time end engine so it will know to calculate the new value
	o.timeEndEngine.setCurrentFlowStartTime(value)
	return nil
}

// GetOffset implements the GetOffset function of FieldEngineIF.
func (o *TimeStartEngine) GetOffset() uint16 {
	return o.par.Offset
}

// GetSize implements the GetSize function of FieldEngineIF.
func (o *TimeStartEngine) GetSize() uint16 {
	return o.par.Size
}

/* ------------------------------------------------------------------------------
							Time End Engine
--------------------------------------------------------------------------------*/
// TimeEndEngineParams represents the paramaters for a TimeEndEngine.
type TimeEndEngineParams struct {
	Size                uint16 `json:"size"`                                       // Size of the time field in bytes. Can be 4 or 8.
	Offset              uint16 `json:"offset"`                                     // Offset in which to write in the packet
	TimeStartEngineName string `json:"time_start_engine_name" validate:"required"` // Name of the time start engine.
	DurationMin         uint64 `json:"duration_min" validate:"required"`           // Minimal duration of this flow in milliseconds.
	DurationMax         uint64 `json:"duration_max" validate:"required"`           // Maximal duration of this flow in milliseconds.
	// This flow will have a duration between the minimal and maximal duration provided. Value is randomly generated.
}

// TimeEndEngine represents an engine for time end fields. (relative to uptime for example, or absolute Unix time).
// This engine should be used together with a TimeStartEngine.
type TimeEndEngine struct {
	par              *TimeEndEngineParams // Parameters
	mgr              *FieldEngineManager  // Engine Manager
	currentFlowStart uint64               // Relative time since this flow began. This field is updated by the time start engine.
	currentFlowEnd   uint64               // Relative time for this flow to end. This value we generate.
	timeStartEngine  *TimeStartEngine     // Relative Time start engine
}

// CreateTimeEndEngine
func CreateTimeEndEngine(params *fastjson.RawMessage, mgr *FieldEngineManager) (FieldEngineIF, error) {
	// parse the json
	p := TimeEndEngineParams{}
	err := mgr.tctx.UnmarshalValidate(*params, &p)
	if err != nil {
		mgr.counters.invalidJson++
		return nil, err
	}
	// create and return new engine
	return NewTimeEndEngine(&p, mgr)
}

// NewTimeEndEngine creates a new TimeStart engine. This engine can be used for fields such as
// flowEndSysUpTime etc.
func NewTimeEndEngine(params *TimeEndEngineParams, mgr *FieldEngineManager) (*TimeEndEngine, error) {
	o := new(TimeEndEngine)
	o.mgr = mgr
	err := o.validateParams(params)
	if err != nil {
		// validate has already set the errors of the manager
		return nil, err
	}
	o.par = params
	return o, nil
}

// validateParams validates that the parameters are correct.
func (o *TimeEndEngine) validateParams(params *TimeEndEngineParams) (err error) {
	if params.Size != 4 && params.Size != 8 {
		o.mgr.counters.invalidSize++
		err = fmt.Errorf("Size for this engine must be 4 or 8. Invalid size %v.\n", params.Size)
	}

	if params.DurationMin > params.DurationMax {
		o.mgr.counters.maxSmallerThanMin++
		err = fmt.Errorf("Duration min %v is greater than Duration max %v.\n", params.DurationMin, params.DurationMax)
	}

	if params.Size == 4 && params.DurationMax > 0xFFFFFFFF {
		o.mgr.counters.sizeTooSmall++
		err = fmt.Errorf("Size too small, can't represent max value %v  with %v bytes.\n", params.DurationMax, params.Size)
	}

	return err
}

// setCurrentFlowStartTime is a setter provided to the Time start engine to set the current flow timestamp.
func (o *TimeEndEngine) setCurrentFlowStartTime(currentFlowStartTime uint64) {
	o.currentFlowStart = currentFlowStartTime
}

// getFlowEndValue generates a relative timestamp for end of this timeflow.
func (o *TimeEndEngine) getFlowEndValue() (value uint64) {

	// Generates a uint64 with uniform distribution.
	// Converts the generated value to a value in the domain [min-max]. This way we get an ipg.
	genValue := rand.Uint64()
	duration := o.par.DurationMin + (genValue % (o.par.DurationMax - o.par.DurationMin + 1))
	// Assumes the TimeEnd engine has updated the previous flow end.
	value = o.currentFlowStart + duration

	return value
}

// Update implements the Update function of FieldEngineIF.
func (o *TimeEndEngine) Update(b []byte) error {

	// Get the Time Start engine. This can't be done upon creation as the engine might not be created yet.
	if o.timeStartEngine == nil {
		timeEndEngine, ok := o.mgr.engines[o.par.TimeStartEngineName]
		if !ok {
			return fmt.Errorf("TimeStart engine name %v not found in engine manager database. Must provide this engine.\n", o.par.TimeStartEngineName)
		}
		o.timeStartEngine, ok = timeEndEngine.(*TimeStartEngine)
		if !ok {
			o.mgr.counters.badEngineType++
			return fmt.Errorf("Failed converting engine %v to TimeStart. Make sure the engine type is corrent.\n", o.par.TimeStartEngineName)
		}
	}

	// Get a value for the end of this flow.
	value := o.getFlowEndValue()

	// Update the buffer with this value.
	switch o.par.Size {
	case 4:
		binary.BigEndian.PutUint32(b, uint32(value))
	case 8:
		binary.BigEndian.PutUint64(b, value)
	default:
		o.mgr.counters.invalidSize++
		return errors.New("Size should be 1, 2, 4 or 8.")
	}

	// Update the start engine so it will know how to calculate the IPG.
	o.timeStartEngine.setPreviousFlowEndTime(value)
	return nil
}

// GetOffset implements the GetOffset function of FieldEngineIF.
func (o *TimeEndEngine) GetOffset() uint16 {
	return o.par.Offset
}

// GetSize implements the GetSize function of FieldEngineIF.
func (o *TimeEndEngine) GetSize() uint16 {
	return o.par.Size
}

/* ------------------------------------------------------------------------------
							URL Histogram
--------------------------------------------------------------------------------*/
// HistogramURLEntry represents an URL which can be used as an entry for the
// HistogramEngine. This entry can be picked with probability prob.
type HistogramURLEntry struct {
	Schemes     []string `json:"schemes"`        // List of schemes
	Hosts       []string `json:"hosts"`          // List of hosts
	Paths       []string `json:"paths"`          // List of paths
	Queries     []string `json:"queries"`        // List of queries
	RandomQuery bool     `json:"random_queries"` // Should randomly generate queries
	Prob        uint32   `json:"prob"`           // Probability for this entry to be picked
}

// longestString receives a string slice and returns the longest string in that slice.
func longestString(stringList []string) (longest string) {
	for _, str := range stringList {
		if len(str) > len(longest) {
			longest = str
		}
	}
	return longest
}

// validateParams ensures a HistogramURLEntry is valid. For a valid entry we must have at
// least one scheme and one host. We need to validate that the longest url we can generate
// is not longer than the provided size.
func (o *HistogramURLEntry) validateParams(mgr *FieldEngineManager, size uint16) error {
	if o.Prob == 0 {
		// Probability 0 can be okay too but let's be strict and eliminate redundant values.
		mgr.counters.invalidHistogramEntry++
		return fmt.Errorf("Invalid probability 0, please remove this entry.\n")
	}
	if len(o.Schemes) == 0 {
		mgr.counters.emptyList++
		mgr.counters.invalidHistogramEntry++
		return fmt.Errorf("Schemes list can't be empty.\n")
	}
	if len(o.Hosts) == 0 {
		mgr.counters.emptyList++
		mgr.counters.invalidHistogramEntry++
		return fmt.Errorf("Hosts list can't be empty.\n")
	}
	if len(o.Queries) != 0 && o.RandomQuery {
		mgr.counters.invalidHistogramEntry++
		return fmt.Errorf("Can't have a list of queries and random queries set together. Please use only one.\n")
	}
	longestScheme := longestString(o.Schemes)
	longestHost := longestString(o.Hosts)
	longestPath := longestString(o.Paths)
	if !o.RandomQuery {
		longestQuery := longestString(o.Queries)
		u := &url.URL{
			Scheme:   longestScheme,
			Host:     longestHost,
			Path:     longestPath,
			RawQuery: longestQuery,
		}
		urlString := u.String()
		byteString := []byte(urlString)
		if len(byteString) > int(size) {
			mgr.counters.sizeTooSmall++
			return fmt.Errorf("Size %v is not enough to encode the longest URL %v. Please provide size of at least %v bytes.\n", size, urlString, len(byteString))
		}
	} else {
		u := &url.URL{
			Scheme: longestScheme,
			Host:   longestHost,
			Path:   longestPath,
		}
		urlString := u.String()
		byteString := []byte(urlString)
		if len(byteString)+3 > int(size) {
			// + 3 for `?q=`` and an empty query
			mgr.counters.sizeTooSmall++
			return fmt.Errorf("Size %v is not enough to encode the longest URL %v with a random query. Please provide size of at least %v bytes.\n", size, urlString, len(byteString)+3)
		}
	}
	return nil
}

func (o *HistogramURLEntry) getQuery(size uint16) (query string) {
	if len(o.Queries) != 0 {
		// queries doesn't necessarily need to be provided.
		if rand.Intn(len(o.Queries)+1) != len(o.Queries) {
			// Even if queries are provided, we don't need to choose them (empty query can be fine too).
			// Pick the empty query with uniform distribution over other queries.
			query = o.Queries[rand.Intn(len(o.Queries))]
		}
	} else if o.RandomQuery == true {
		// build random query of random size
		querySize := rand.Intn(int(size + 1)) // + 1 to generate in [0, n] instead of [0, n)
		dictionary := "aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ0123456789"
		var s strings.Builder
		s.Grow(querySize)
		for i := 0; i < querySize; i++ {
			rndChar := dictionary[rand.Intn(len(dictionary))]
			fmt.Fprintf(&s, "%c", rndChar)
		}
		query = s.String()
	}
	return query
}

// generateURL generates randomly an URL from the list of given schemes, hosts, paths and queries.
// Paths and queries can be picked or not, while schemes and hosts and always picked to be part of the url.
// All the generations are done uniformly from the lists.
func (o *HistogramURLEntry) generateURL(size uint16) *url.URL {
	var scheme, host, path string
	scheme = o.Schemes[rand.Intn(len(o.Schemes))]
	host = o.Hosts[rand.Intn(len(o.Hosts))]
	if len(o.Paths) != 0 {
		// paths doesn't necessarily need to be provided.
		if rand.Intn(len(o.Paths)+1) != len(o.Paths) {
			// Even if paths are provided, we don't need to choose them (empty path can be fine too).
			// Pick the empty path with uniform distribution over other paths.
			path = o.Paths[rand.Intn(len(o.Paths))]
		}
	}
	u := &url.URL{
		Scheme: scheme,
		Host:   host,
		Path:   path,
	}
	q := u.Query()
	queryMaxSize := size - uint16(len([]byte(u.String()))) - 3 // -2 for encoding ?q= in query
	query := o.getQuery(queryMaxSize)
	if o.RandomQuery == true {
		q.Set("q", query)
		u.RawQuery = q.Encode()
	} else {
		u.RawQuery = query
	}

	return u

}

// GetValue gets a newly generated URL randomly and returns it as a byte buffer.
func (o *HistogramURLEntry) GetValue(size uint16) (b []byte, err error) {
	url := o.generateURL(size)
	urlBytes := []byte(url.String())

	if int(size) < len(urlBytes) {
		return nil, fmt.Errorf("Size %v is too small for generated url %v.\n", size, url.String())
	}

	return urlBytes, nil
}

// GetProb returns the probability for this entry to be picked in the histogram engine.
func (o *HistogramURLEntry) GetProb() uint32 {
	return o.Prob
}

// HistogramURL64Params is used for parsing the input JSON and create the entries.
type HistogramURLParams struct {
	HistogramEngineCommonParams                     // common params
	Entries                     []HistogramURLEntry `json:"entries"` // slice of entries
}

// CreateHistogramURLEngine creates an Histogram Engine whose entries are URL builders.
func CreateHistogramURLEngine(params *fastjson.RawMessage, mgr *FieldEngineManager) (eng FieldEngineIF, err error) {
	// unmarshal the data
	p := HistogramURLParams{}
	err = fastjson.Unmarshal(*params, &p)
	if err != nil {
		mgr.counters.invalidJson++
		return nil, err
	}

	// create general params
	var histParams HistogramEngineParams
	histParams.Size = p.Size
	histParams.Offset = p.Offset
	for i := range p.Entries {
		// validate entries.
		err := p.Entries[i].validateParams(mgr, p.Size)
		if err != nil {
			// errors are already set in the entry.
			return nil, err
		}
		histParams.Entries = append(histParams.Entries, &p.Entries[i])
	}

	// create and return new engine
	return NewHistogramEngine(&histParams, mgr)
}

func init() {
	// Register all the field engine types.
	fieldEngineRegister("uint", CreateUIntEngine)
	fieldEngineRegister("uint_list", CreateUIntListEngine)
	fieldEngineRegister("string_list", CreateStringListEngine)
	fieldEngineRegister("histogram_uint", CreateHistogramUInt32Engine)
	fieldEngineRegister("histogram_uint_range", CreateHistogramUInt32RangeEngine)
	fieldEngineRegister("histogram_uint_list", CreateHistogramUInt32ListEngine)
	/*
		uint64 is an exception and needs special handlers
	*/
	fieldEngineRegister("histogram_uint64", CreateHistogramUInt64Engine)
	fieldEngineRegister("histogram_uint64_range", CreateHistogramUInt64RangeEngine)
	fieldEngineRegister("histogram_uint64_list", CreateHistogramUInt64ListEngine)

	fieldEngineRegister("time_start", CreateTimeStartEngine)
	fieldEngineRegister("time_end", CreateTimeEndEngine)

	fieldEngineRegister("histogram_url", CreateHistogramURLEngine)
}
